package miage.rest.db;

import miage.rest.db.bean.BeanProcessor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.dbutils.BasicRowProcessor;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;

/**
 * Classe d'accès à la base de données
 * 
 * @author Schwartz Michaël
 */
public class Database {
	private final DataSource dataSource;

	public Database(DataSource ds) {
		this.dataSource = ds;
	}

	/**
	 * Retourne une nouvelle connexion à la base de donnée
	 * /!\ Attention à chaque appel de getNewConnection, la connexion peut ne pas
	 * être la même (par exemple dans le cas d'utilisation d'un pool de connexion)
	 *
	 * @return une connexion
	 * @throws SQLException
	 */
	public Connection getNewConnection() throws SQLException {
		return dataSource.getConnection();
	}

	/**
	 * Retourne la totalité des résultats de la requête passée en paramètre
	 * Les résultats sont retournés sous forme d'une liste de Map<String,Object>
	 *
	 * @param sql
	 * @return
	 * @throws SQLException
	 */
	public List<Map<String,Object>> fetchAll(String sql) throws SQLException {
		QueryRunner queryRunner = new QueryRunner(dataSource);
		ResultSetHandler<List<Map<String,Object>>> rsh = new MapListHandler();
		return queryRunner.query(sql, rsh);
	}

	/**
	 * Retourne la totalité des résultats de la requête sous forme d'une liste de bean du type
	 * spécifié
	 * 
	 * @param <T>
	 * @param sql
	 * @param bean
	 * @return
	 * @throws SQLException
	 */
	public <T> List<T> fetchAll(String sql, Class<T> bean, Object... params) throws SQLException {
		QueryRunner queryRunner = new QueryRunner(dataSource);
		ResultSetHandler<List<T>> rsh = new BeanListHandler<T>(bean, new BasicRowProcessor(new BeanProcessor()));
		return queryRunner.query(sql, rsh, params);
	}

	/**
	 * Retourne la première ligne de résultat de la requète sous forme d'une Map<String,Object>
	 * @param sql
	 * @return
	 * @throws SQLException
	 */
	public Map<String,Object> fetchRow(String sql, Object... params) throws SQLException {
		QueryRunner queryRunner = new QueryRunner(dataSource);
		ResultSetHandler<Map<String,Object>> rsh = new MapHandler();
		return queryRunner.query(sql, rsh, params);
	}


	/**
	 * Retourne une ligne de résultat de la requete sous forme de bean
	 * @param <T>
	 * @param sql
	 * @param bean
	 * @return
	 * @throws SQLException
	 */
	public <T> T fetchRow(String sql,Class<T> bean, Object... params) throws SQLException {
		QueryRunner queryRunner = new QueryRunner(dataSource);
		ResultSetHandler<T> rsh = new BeanHandler<T>(bean,new BasicRowProcessor(new BeanProcessor()));
		return queryRunner.query(sql, rsh, params);
	}


	/**
	 * Retourne la première colonne de la première ligne de résultat
	 * @param sql
	 * @return
	 * @throws SQLException
	 */
	public Object fetchOne(String sql) throws SQLException {
		QueryRunner queryRunner = new QueryRunner(dataSource);
		ResultSetHandler<Object[]> rsh = new ArrayHandler();
		Object[] firstRow = queryRunner.query(sql, rsh);
		
		if (firstRow != null && firstRow.length > 0) {
			return firstRow[0];
		}

		return null;
	}

	/**
	 * Execution d'une requête de type UPDATE
	 * @param sql
	 * @param params
	 * @return
	 * @throws SQLException
	 */
	public int update(String sql, Object ...params) throws SQLException {
		return new QueryRunner(dataSource).update(sql,params);
	}

	/**
	 * Execution d'une requête de type DELETE
	 * @param sql
	 * @param params
	 * @return
	 * @throws SQLException
	 */
	public int delete(String sql, Object ...params) throws SQLException {
		return new QueryRunner(dataSource).update(sql,params);
	}
	
	/**
	 * Exécute une requete de type INSERT, UPDATE, DELETE et retourne une instance de
	 * UpdateResult contenant le nombre de ligne afféctée et éventuellement la clé générée
	 * de type Integer
	 *
	 * @require conn.isValid();
	 * @require sql != null && !sql.isEmpty();
	 *
	 * @param conn
	 * @param sql
	 * @param params
	 * @return
	 * @throws SQLException
	 */
	public UpdateResult<Integer> update(Connection conn, String sql, Object ...params) throws SQLException {

		UpdateResult<Integer> updateResult = new UpdateResult<Integer>();

		QueryRunner queryRunner = new QueryRunner();

		PreparedStatement statement = conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
		queryRunner.fillStatement(statement, params);

		int affectedRows = statement.executeUpdate();
		ResultSet generatedKeysResultSet = statement.getGeneratedKeys();
		
		updateResult.setAffectedRows(affectedRows);

		if (generatedKeysResultSet != null && generatedKeysResultSet.next()) {
			updateResult.setGeneratedKeys(generatedKeysResultSet.getInt(1));
		}

		return updateResult;
	}
}
